from gevent import sleep
from gevent_zeromq import zmq
from utils import config, Logged, GAnswer
from xmlrpc import XmlRPCServer
from time import time
from config import Covert_Helper
import yaml

class GAction(Logged, Covert_Helper):
	oid = 0	
	working = True
	exp_timeout = 0
	timeout = 0.0
	rate = 0.0
	keep = True
	def __init__(self, pool, code = '', loop = False, seq = None, from_man = False):
		self.pool = pool
		self.loop = loop
		if seq is not None:
			self.seq = config.get_obj(seq)
		else:
			self.seq = None
		self.from_man = from_man
		if code.find('\n') == -1: code += '\n' # fix for one liners
		lines = code.split('\n')
		self.lines = []
		for l in lines:
			if len(l) == 0 or l[0] == '#':
				continue
			a = l.split()
			if a[0].find('.') != -1: # set command 
				a[0] = a[0][:-1] # rid out ':' at the end
				self.lines.append([a[0], yaml.load(l)[a[0]]])
			elif len(a) > 2:
				b = a[:2]
				for i in a[2:]:
					b.append(yaml.load('v: ' + i)['v'])
				self.lines.append(b)
			else:
				self.lines.append(a)
	def start(self):
		return self.run()

	def run(self):
		self.working = True
		if self.loop:
			return self.run_loop()
		j = self.pool.apply_async(self.run2)
		while self.working:
			if j.ready():
				return j.get()
			sleep(0.3)

	def run_loop(self):
		st = 1.0 / self.rate
		t1 = time()
		i = 0
		while self.working:
			t2 = time()
			self.run2()
			sleep(st - (t2 - t1 - st * i))
			i += 1

	def _run(self, a):
		o = config.get_obj(a[0])
		if o is None:
			s = 'no such object: ' + a[0]
			self.log_error(s)
			a = GAnswer(False, load = s)
			return a
		obj_type = getattr(o, 'obj_type', None)

		# default action for action is start
		if (obj_type == 'action' or obj_type == 'test') and len(a) == 1:
			a.append('start')

		func = None
		try:
			func = getattr(o, a[1])
		except AttributeError, e:
			self.log_error(e)
			return GAnswer(False, load = 'wrong arg: ' + a[1])
		else:
			r = None
			if len(a) > 2:
				r = func(*a[2:])
			else:
				r = func()
			if isinstance(r, GAnswer):
				r.name = a[0]
			return r

	def run2(self):
		for i in self.lines:
			self.log('exec: %s' % i)
			cmd = i[0]
			if cmd == 'sleep':
				sleep(float(i[1]))
			elif cmd == 'print' or i[0][0] == '#':
				pass
			elif cmd == 'stop':
				self._run(['main', 'stop'])
				break
			elif cmd == 'status':
				a = self._run(['main', 'status'])
				if self.from_man:
					return a
			elif '.' in i[0]: # set command
				a = i[0].split('.')
				o = config.get_obj(a[0])
				if o is None:
					self.log_error('No such object %s' % a[0])
					return 'no such prop'
				if a[1] not in config.get_schema(o.obj_type)['props']:
					s = 'No such properies %s' % a[1]
					self.log_error(s)
					if self.from_man:
						return GAnswer(False, load = s)
					continue
				obj_type = config.get_schema(o.obj_type)['props'][a[1]]['type']
				v = self.convert_value(i[1], obj_type)
				if v is None:
					return
				setattr(o, a[1], v)
				setattr(o, '_changed', True)
				s = 'set: %s@%s = %s' % (a[0], a[1] ,v)
				self.log(s)
				if self.from_man:
					return GAnswer(True, load = s)
			elif self.loop:
				self.pool.spawn(self._run, i)
			elif i[0] == '+':
				# with parallel
				self.pool.spawn(self._run, i[1:])
				if self.from_man:
					return GAnswer(True, load = 'Command sent to background.')
			else:
				# no parallel
				ans = self._run(i)
				if isinstance(ans, GAnswer):
					self.loga(ans)
					if not ans.ok and not self.keep:
						self.loga(GAnswer(False, 'Stop script execution because found error and keep = False'))
						self._run(['main', 'stop'])
						break
				if self.from_man:
					# single line action from remote manager needs instant response
					return ans
		return GAnswer()

	# used for evaluate time needed for real execution
	def run1(self):
		t = 0.0
		for i in self.lines:
			cmd = i[0]
			if cmd == 'sleep':
				t += float(i[1])
			elif cmd == 'print' or i[0] == '#' or cmd == 'stop' or '.' in i[0] or i[0] == '+':
				pass
			elif cmd == 'status':
				t += 0.5
			else:
				# TODO: count for nested TEST etc...
				o = config.get_obj(i[0])
				if o is None:
					self.exp_timeout = - 1.0
					return -1
				try:
					t += getattr(o, 'timeout', None)
				except AttributeError, e:
					t += 0.0
		self.exp_timeout = t
		self.log_debug('run1 timeout: %f' % t)
		return t

	def run_wait(self, socket):
		from gevent import Timeout
		with Timeout(self.exp_timeout):
			try:
				r = self.run()
				socket.send(zmq.Message(str(r)))
				#print 'get %s' % str(r)
			except Timeout:
				socket.send(zmq.Message('timeout'))
			except Exception, e:
				print e

	def stop(self):
		self.working = False

class GManager(Logged):
	name = 'zmq'
	def __init__(self, pool, addr):
		self.pool = pool
		context = zmq.Context()
		self.socket = context.socket(zmq.REP)
		self.socket.bind(addr)
		self.greenlet = pool.spawn(self.loop)

	def loop(self):
		while True:
			a = self.socket.recv_pyobj()
			self.log_debug('req_in: %s' % a)
			action = GAction(self.pool, a + '\n', from_man = True)
			t = action.run1()
			if t > 1.0:
				self.socket.send(zmq.Message(str(t)))
				ok = self.socket.recv_pyobj()
				#TODO if ok ...
				action.run_wait(self.socket)
			else:
				# quick done
				ans = action.run()
				self.socket.send(zmq.Message('0\n' + str(ans)))
			self.log_debug('req_out: %s' % a)
	def stop(self):
		self.greenlet.kill()
		self.socket.close()

class XmlRPCManager(XmlRPCServer):
	name = 'xmlrpc'
	def cmd(self, a):
		self._log('get: ' + a)
		a = GAction(self.pool, a + '\n', from_man = True)
		a.run()
		return 'ok'
